package de.recipeminer.environments;

import com.google.common.base.Charsets;
import com.google.common.base.Joiner;
import org.apache.log4j.Logger;

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;

/**
 * @autor: Markus Ackermann
 */
public class FileFinder {
    private static Logger logger = Logger.getLogger(FileFinder.class);
    private String subPath, fallbackSubPath;
    private ClassLoader classLoader;

    public FileFinder(String subPath, String fallbackSubPath) {
        this.subPath = subPath;
        this.fallbackSubPath = fallbackSubPath;
        classLoader = Thread.currentThread().getContextClassLoader();
    }

    public FileFinder(String subPath) {
        this(subPath, null);
    }

    /**
     * Uses the System Properties "user.dir" that should point
     * to the current working directory associated with JVM.
     *
     * @return current working directory
     */
    public static String getProjectParentPath() {
        String s = (String) System.getProperties().get("user.dir");
        File f = new File(s);
        return f.getParent();
        //System.out.println(s.getClass());

        /*  String parent_directory_name = f.getParent();
            File sdfsdf;
            URI uri = f.toURI();
        while (uri.) */
    }

    public File getFile(String path) {
        File file = new File(path);
        if (file.isAbsolute()) {
            if (file.exists()) {
                return file;
            } else {
                throw new UnableToLocateException(path);
            }
        } else {
            return getRelativeFile(path);
        }
    }

    private File getRelativeFile(String path) {
        File file = resolveRelFilePath(getSubPath(path));
        if (!file.exists() && fallbackSubPath != null) {
            file = resolveRelFilePath(getFallbackSubpath(path));
        }
        if (!file.exists()) {
            file = attempResourceInFileSystem(path);
        }
        return file;
    }

    private File attempResourceInFileSystem(String literalPath) {
        URL url = getResource(literalPath);
        if (url == null) {
            throw new UnableToLocateException(literalPath);
        } else {
            File file;
            try {
                file = new File(url.toURI());
            } catch (URISyntaxException ex) {
                throw new UnableToLocateException(literalPath, ex);
            }
            if (!file.exists()) {
                throw new UnableToLocateException(literalPath);
            }
            return file;
        }
    }

    public URL getResource(String descriptor) {
        URL url = classLoader.getResource(getSubPath(descriptor));
        if (url == null) {
            url = classLoader.getResource(getFallbackSubpath(descriptor));
        }
        if (url == null) {
            throw new UnableToLocateException(descriptor);
        }
        return url;
    }

    public URI getFileOrResource(String descriptor) {
        try {
            File file = getFile(descriptor);
            return file.toURI();
        } catch (UnableToLocateException ule) {
            try {
                return getResource(descriptor).toURI();
            } catch (URISyntaxException use) {
                throw new UnableToLocateException(descriptor, use);
            }
        }
    }

    public InputStream getStream(String descriptor) {
        try {
            return getFileOrResource(descriptor).toURL().openStream();
        } catch (Exception e) {
            throw new UnableToLocateException(descriptor, e);
        }
    }

    public BufferedReader getReader(String descriptor) {
        return new BufferedReader(new InputStreamReader(getStream(descriptor), Charsets.UTF_8));
    }

    private String getSubPath(String path) {
        return subPath.length() > 0 ? Joiner.on('/').join(subPath, path) : path;

    }

    private String getFallbackSubpath(String path) {
        if (fallbackSubPath != null) {
            return fallbackSubPath.length() > 0 ? Joiner.on('/').join(fallbackSubPath, path) : path;
        } else {
            return null;
        }
    }

    /**
     * @param relPath relative path using '/' als path delimiter irrespective of host file system
     * @return  File revolved with respect to current working directory
     */

    private File resolveRelFilePath(String relPath) {
        URI rootURI = new File(".").toURI();
        return new File(rootURI.resolve(relPath));
    }
    
    /**
     * Describes an Error of the {@link FileFinder}s when it was unable to find a requested resource (file)
     * neither in the context-specific folder nor in an optionally defined fallback location, neither as real
     * file nor as classpath resource.
     */
    class UnableToLocateException extends RuntimeException {
        public UnableToLocateException(String resourceDescriptor) {
            super(String.format("Unable to locate '%s' using the file system and classloader %s.",
                    resourceDescriptor,
                    Thread.currentThread().getContextClassLoader().getClass().getName()));
        }

        public UnableToLocateException(String resourceDescriptor, Throwable cause) {
            super(String.format("Unable to locate '%s' using the file system classloader %s.",
                    resourceDescriptor,
                    Thread.currentThread().getContextClassLoader().getClass().getName()), cause);
        }
    }
}
